﻿namespace HostTracker.Azure.Management

open System
open System.ServiceModel
open System.ServiceModel.Web
open System.Runtime.Serialization
open System.Configuration
open System.Net
open HostTracker.Azure.Management.FSharpExtensions
open Microsoft.FSharp.Core

(*
<?xml version="1.0" encoding="utf-8"?>
  <Operation xmlns="http://schemas.microsoft.com/windowsazure">
    <ID>request-id</ID>
    <Status>InProgress|Succeeded|Failed</Status>
    <!--Response includes HTTP status code only if the operation succeeded or failed -->
    <HttpStatusCode>http-status-code-for-asynchronous-operation</HttpStatusCode>
    <!--Response includes additional error information only if the operation failed -->
    <Error>
      <Code>error-code</Code>
      <Message>error-message</Message>
    </Error>
  </Operation>
*)
type OperationStatus = InProgress | Succeeded | Failed
with
    override x.ToString() = 
        Reflection.unionToStringPlain x
    static member Parse(str) =
        Reflection.stringToUnionPlain<OperationStatus> str
(*
[<Interface>]
type ICloudOperationObserver = 
    abstract OnException: e:Exception*canContinue:bool -> bool
    abstract OnIntermediateError: CloudResponce<unit, ServiceManagementError> -> bool
    abstract OnResult: CloudResponce<unit, ServiceManagementError> -> unit
*)
[<DataContract(Name = "Operation", Namespace = "http://schemas.microsoft.com/windowsazure")>]
type OperationResult() = 
    let mutable id =""
    let mutable status = InProgress
    let mutable httpCode = 200
    let mutable error = ServiceManagementError()
    [<DataMember(Name = "ID", Order = 1)>]
    member x.Id with get () = id and set v = id <- v
    [<DataMember(Name = "Status", Order = 2)>]
    member private x.StatusStr 
        with get () = status.ToString() and 
            set v = status <- OperationStatus.Parse v
    member x.Status = status
    [<DataMember(Name = "HttpStatusCode", Order = 3)>]
    member x.HttpCode with get () = httpCode and set v = httpCode <- v
    [<DataMember(Name = "Error", Order = 4, IsRequired = false)>]
    member x.Error with get () = error and set v = error <- v

type OperationResultHandlers = {
    onResult: string -> CloudResponce<unit, ServiceManagementError> -> unit
    onStartFail: CloudFailResponce<ServiceManagementError> -> unit
    onStartException: Exception -> unit
    onIntermediateFail: string -> CloudFailResponce<ServiceManagementError> -> bool // continue or not
    onIntermediateResult: string -> CloudSuccessResponce<OperationResult> -> unit
    onIntermediateException: string -> Exception -> bool // continue or not    
}
with
    static member Empty = 
        { 
            onResult = fun _ _ -> ()
            onStartFail = fun _ -> ()
            onStartException = fun _ -> ()
            onIntermediateFail = fun _ _ -> false
            onIntermediateResult = fun _ _ -> ()
            onIntermediateException = fun _ _ -> false
        }
    static member ConsoleTracers = 
        {
            onResult = fun reqId ->
                function
                | Success resp ->
                    printfn "Operation %A completed successfuly" reqId
                | Fail resp ->
                    printfn "Cloud request failed with error: %A (Message = %A)" 
                        resp.data.Code resp.data.Message
            onStartException = fun e ->
                printfn "Exception %A occured during start" e
            onIntermediateFail = fun reqId resp ->
                printfn "ReqId = %A. Intermediate fail occured: %A (Message=%A)" 
                    reqId resp.data.Code resp.data.Message
                true
            onIntermediateException = fun reqId e ->
                printfn "ReqId = %A. Intermediate exception %A" reqId e
                true
            onIntermediateResult = fun reqId _ -> printfn "ReqId = %A. In progress..." reqId
            onStartFail = fun resp -> 
                printfn "Cloud request failed with error: %A (Message = %A)" 
                    resp.data.Code resp.data.Message
        }
    static member FromFuncs(onResult: Action<string, CloudResponce<unit, ServiceManagementError>>,
                            onStartFail: Action<CloudFailResponce<ServiceManagementError>>,
                            onStartException: Action<Exception>,
                            onIntermediateResult: Action<string, CloudSuccessResponce<OperationResult>>,
                            onIntermediateFail: Func<string, CloudFailResponce<ServiceManagementError>, bool>,
                            onIntermediateException: Func<string, Exception, bool>) =
        {
            onResult = FuncConvertExtensions.ToFSharpFunc(onResult)
            onStartFail = FuncConvert.ToFSharpFunc(onStartFail)
            onStartException = FuncConvert.ToFSharpFunc(onStartException)
            onIntermediateResult = FuncConvertExtensions.ToFSharpFunc(onIntermediateResult)
            onIntermediateFail = FuncConvertExtensions.ToFSharpFunc(onIntermediateFail)
            onIntermediateException = FuncConvertExtensions.ToFSharpFunc(onIntermediateException)
        }
    member x.ChangeOnResultAction(onResult: Action<string, CloudResponce<unit, ServiceManagementError>>) =
        { x with onResult = FuncConvertExtensions.ToFSharpFunc(onResult)}
    member x.ChangeOnStartErrorAction(onStartFail: Action<CloudFailResponce<ServiceManagementError>>) =
        { x with onStartFail = FuncConvert.ToFSharpFunc(onStartFail) }
    member x.ChangeOnStartExceptionAction(onStartException: Action<Exception>) =
        { x with onStartException = FuncConvert.ToFSharpFunc(onStartException) }
    member x.ChangeOnIntermediateResultAction(onIntermediateResult: Action<string, CloudSuccessResponce<OperationResult>>) =
        { x with onIntermediateResult = FuncConvertExtensions.ToFSharpFunc(onIntermediateResult) }
    member x.ChangeOnIntermediateFailAction(onIntermediateFail: Func<string, CloudFailResponce<ServiceManagementError>, bool>) =
        {x with onIntermediateFail = FuncConvertExtensions.ToFSharpFunc(onIntermediateFail) }
    member x.ChangeOnIntermediateFailAction(onIntermediateException: Func<string, Exception, bool>) =
        {x with onIntermediateException = FuncConvertExtensions.ToFSharpFunc(onIntermediateException) }
    
type BaseManagement<'IChannel when 'IChannel: not struct> (includeSubscribtionId) as x = 
    inherit ClientBase<'IChannel>()
    static let interval = 
        try
            let interval = Int32.Parse(ConfigurationManager.AppSettings.["cloudLongOperationInterval"])
            Math.Max(2000, interval)
        with _ -> 5000 
    static let id = 
        try ConfigurationManager.AppSettings.["azureSubscribtionId"] with _ -> 
            //failwith "Subscribtion Id was not specified in .config file. Check appConfig section for key = azureSubscribtionId and try again"
            Guid.Empty.ToString() // Guid.Empty indicate error in config
    static let adress: ref<EndpointAddress> = ref null //!!! one 'IChannel has one address
    do      
        match includeSubscribtionId, !adress with
        | true, null ->
            let uri = Uri(x.Endpoint.Address.Uri, id)
            let adressVal = 
                EndpointAddress(uri, 
                    x.Endpoint.Address.Identity, x.Endpoint.Address.Headers, 
                    x.Endpoint.Address.GetReaderAtMetadata(), x.Endpoint.Address.GetReaderAtExtensions())
            x.Endpoint.Address <- adressVal
            adress := adressVal
        | true, adressVal -> x.Endpoint.Address <- adressVal        
        | false, _ -> ()
    let channel = x.Channel
    member x.AsyncBegin withBody apiVersion beginFunc =
        (fun (rc, _) -> 
            use contextScope = new OperationContextScope(x.InnerChannel)
            let webContext = WebOperationContext(OperationContext.Current)
            webContext.OutgoingRequest.Headers.Add("x-ms-version", apiVersion)   
            if withBody then               
                webContext.OutgoingRequest.Headers.Add("Content-Type", "application/xml")
            beginFunc channel (rc, (OperationContext.Current :> obj)))
    member x.AsyncEnd<'T, 'Error> endFunc = 
        (fun (iac: IAsyncResult) -> 
            let context = iac.AsyncState :?> OperationContext
            use contextScope = new OperationContextScope(context)
            let webContext = WebOperationContext(OperationContext.Current)
            let result = 
                fun () ->
                    let data = endFunc channel iac
                    {
                        httpCode = (webContext.IncomingResponse.StatusCode |> int)
                        requestId = webContext.IncomingResponse.Headers.["x-ms-request-id"]
                        data = data
                    } |> Success
                |> CloudErrorProcessing.failControll<'T, 'Error>
            result)
    member x.LongOperationCheckLoop(reqId, (check: string -> Async<CloudResponce<OperationResult, _>>), handlers: OperationResultHandlers) = 
        let rec checkLoop () = async {    
            try        
                let! res = check reqId
                match res with
                | Success suc when suc.data.Status = Failed ->
                    let res = Fail { data = suc.data.Error }
                    handlers.onResult reqId res
                | Success suc when suc.data.Status = Succeeded ->
                    let res = Success { httpCode = suc.data.HttpCode; data = (); requestId = suc.requestId }
                    handlers.onResult reqId res
                | Fail fail when not(handlers.onIntermediateFail reqId fail) -> 
                    return ()
                | Success suc when suc.data.Status = InProgress -> // in progress
                    handlers.onIntermediateResult reqId suc
                    do! Async.Sleep interval
                    return! checkLoop ()
                | _ -> 
                    do! Async.Sleep interval
                    return! checkLoop ()
            with 
                | e when handlers.onIntermediateException reqId e ->
                    do! Async.Sleep interval
                    return! checkLoop()
                | _ -> return ()              
        }
        checkLoop ()
    member x.StartLongOperation(opStart: Async<CloudResponce<unit, _>>, check: string -> Async<CloudResponce<OperationResult, _>>, handlers: OperationResultHandlers) = 
        async {            
            try
                let! startResult = opStart
                match startResult with
                | Success responce ->
                    do! x.LongOperationCheckLoop(responce.requestId, check, handlers)                    
                | Fail responce -> 
                    handlers.onStartFail responce
            with e -> 
                handlers.onStartException e
        }
    member x.Sync<'T, 'Error> withBody apiVersion func = 
        use contextScope = new OperationContextScope(x.InnerChannel)
        let webContext = WebOperationContext(OperationContext.Current)
        webContext.OutgoingRequest.Headers.Add("x-ms-version", apiVersion)  
        if withBody then               
            webContext.OutgoingRequest.Headers.Add("Content-Type", "application/xml")              
        fun () ->
            let result = func channel
            {
                httpCode = (webContext.IncomingResponse.StatusCode |> int)
                requestId = webContext.IncomingResponse.Headers.["x-ms-request-id"]
                data = result
            } |> Success
        |> CloudErrorProcessing.failControll<'T, 'Error>        
    static member SubscribtionId = id